﻿using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;

namespace VSTalk.Tools
{
    /// <summary>
    /// Substitutes <see cref="ObservableCollection{T}.CollectionChanged"/> with the callback based event processing
    ///  </summary>
    public static class ObservableCollectionExtension
    {
        private static ISubscription HandleAddRemoveChanges<T>(this ObservableCollection<T> collection, Action<T, ISubscriptionJoiner> added, Action<T, ISubscriptionJoiner> removed, SubscriptionJoiner agg)
        {
            var action = new ActionWrapper<object, NotifyCollectionChangedEventArgs>((sender, args) =>
            {
                if (args.OldItems != null)
                {
                    foreach (var oldItem in args.OldItems)
                    {
                        removed((T)oldItem, agg);
                    }
                }

                if (args.NewItems != null)
                {
                    foreach (var newItem in args.NewItems)
                    {
                        added((T)newItem, agg);
                    }
                }
            });
            collection.CollectionChanged += action.Exec;
            return new Subscription(() =>
            {
                foreach (ISubscription unit in agg)
                {
                    unit.Unsubscribe();
                }
                collection.CollectionChanged -= action.Exec;
            });
        }

        internal static ISubscription HandleAddRemoveChanges<T>(this ObservableCollection<T> collection, Action<T, ISubscriptionJoiner> added, Action<T, ISubscriptionJoiner> removed)
        {
            return HandleAddRemoveChanges(collection, added, removed, new SubscriptionJoiner());
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="collection"></param>
        /// <param name="added">Raises after an object was added to the collection</param>
        /// <param name="removed">Raises after an object was removed from the collection</param>
        /// <returns>Unit for stop listening</returns>
        public static ISubscription HandleAddRemoveChanges<T>(this ObservableCollection<T> collection, Action<T> added, Action<T> removed)
        {
            return HandleAddRemoveChanges(collection, (arg1, prologue) => added(arg1), (arg1, prologue) => removed(arg1));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="collection"></param>
        /// <param name="initial">Iterates through collection</param>
        /// <param name="added">Raises after an object was added to the collection</param>
        /// <param name="removed">Raises after an object was removed from the collection</param>
        /// <returns>The unit to stop listening</returns>
        public static ISubscription HandleCollectionChanges<T>(this ObservableCollection<T> collection,
                Action<T, ISubscriptionJoiner> initial,
                Action<T, ISubscriptionJoiner> added,
                Action<T, ISubscriptionJoiner> removed)
        {
            var aggregated = new SubscriptionJoiner();
            foreach (var item in collection)
            {
                var joiner = new SubscriptionJoiner();
                initial(item, joiner);
                aggregated.Join(new Subscription(() =>
                {
                    foreach (ISubscription unit in joiner)
                    {
                        unit.Unsubscribe();
                    }
                }));
            }
            return HandleAddRemoveChanges(collection, added, removed, aggregated);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="collection"></param>
        /// <param name="initial">Iterates through collection</param>
        /// <param name="added">Raises after an object was added to the collection</param>
        /// <param name="removed">Raises after an object was removed from the collection</param>
        /// <returns>The unit to stop listening</returns>
        public static ISubscription HandleCollectionChanges<T>(this ObservableCollection<T> collection, Action<T> initial, Action<T> added, Action<T> removed)
        {
            foreach (var item in collection)
            {
                initial(item);
            }
            return HandleAddRemoveChanges(collection, added, removed);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="collection"></param>
        /// <param name="initialOrAdd">Iterates through collection and raises after an object was added to the collection</param>
        /// <returns>The unit to stop listening</returns>
        public static ISubscription HandleCollectionChanges<T>(this ObservableCollection<T> collection, Action<T, ISubscriptionJoiner> initialOrAdd)
        {
            return HandleCollectionChanges(collection, initialOrAdd, initialOrAdd, (_1, _2) => { });
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="collection"></param>
        /// <param name="initialOrAdd">Iterates through collection and raises after an object was added to the collection</param>
        /// <param name="removed">Raises after an object was removed from the collection</param>
        /// <returns>The unit to stop listening</returns>
        public static ISubscription HandleCollectionChanges<T>(this ObservableCollection<T> collection, Action<T, ISubscriptionJoiner> initialOrAdd, Action removed)
        {
            return HandleCollectionChanges(collection, initialOrAdd, initialOrAdd, (_1, _2) => removed());
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="collection"></param>
        /// <param name="initialOrAdd">Iterates through collection and raises after an object was added to the collection</param>
        /// <param name="removed">Raises after an object was removed from the collection</param>
        /// <returns>The unit to stop listening</returns>
        public static ISubscription HandleCollectionChanges<T>(this ObservableCollection<T> collection, Action<T> initialOrAdd, Action<T> removed)
        {
            return HandleCollectionChanges(collection, (arg, _) => initialOrAdd(arg), (arg, _) => initialOrAdd(arg), (arg, _) => removed(arg));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="collection"></param>
        /// <param name="removed">Raises after an object was removed from the collection</param>
        /// <returns>The unit to stop listening</returns>
        public static ISubscription HandleRemoveChanges<T>(this ObservableCollection<T> collection, Action<T> removed)
        {
            return HandleAddRemoveChanges(collection, (_1, _2) => { }, (arg1, prologue) => removed(arg1));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="collection"></param>
        /// <param name="any">Iterates through collection and raises after an object was added to or removed from the collection</param>
        /// <returns>The unit to stop listening</returns>
        public static ISubscription HandleAnyChanges<T>(this ObservableCollection<T> collection, Action any)
        {
            return HandleCollectionChanges(collection, _ => any(), _ => any(), _ => any());
        }
    }
}